
#ifndef _WIN32
#include <unistd.h>
#endif
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <semaphore.h>
#include <time.h>
#include <pthread.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/time.h>
#endif
#include "thread_hal.h"

typedef void (*ENTRYPOINT)(void *param);

typedef struct _THREAD_T{
    pthread_t thread;
    int32_t stack_size;
    int32_t priority;
    int32_t policy;
    ENTRYPOINT entry_point;
    void  * arg;
    pthread_mutex_t  wakeup_mutex; /* Thread wakeup mutex */
    pthread_cond_t   wakeup_cond;  /* Thread wakeup condition */
    int32_t sleep;
    int32_t wakeup_signal;
}THREAD_T;

typedef struct _SEM_T{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int   sem;
}Sem_T;

typedef struct _MUTEX_T
{
    pthread_mutex_t mutex;
}MUTEX_T;

typedef struct _HAL_HANDLE
{
    pthread_key_t  thread_key;  /* thread key */
    int32_t  thread_key_init;
    int32_t  thread_cnt;
}HAL_HANDLE;
static HAL_HANDLE g_HAL_Handle;

static void *thread_hal_launch_thread( void *param )
{
    THREAD_T  *pthread = param;

    /* Register with thread local storage */
    pthread_setspecific(g_HAL_Handle.thread_key, pthread);

    /* Enter main loop */
    pthread->entry_point(pthread->arg);

    return NULL;
}

static void thread_hal_init(void)
{
    int ret;

    if(g_HAL_Handle.thread_key_init)
        return;

    /* 1 - create thread key */
    ret = pthread_key_create(&g_HAL_Handle.thread_key, NULL);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): Unable to create thread key[%d]!",
            __FUNCTION__, __LINE__, ret);
        return;
    }

    g_HAL_Handle.thread_key_init = TRUE;

    return;
}
thread_t thread_create(void (*entry_point)( void *), void *arg, int32_t stack_size, int prio)
{
    THREAD_T *pthread = NULL;
    int      ret = 0;
    pthread_attr_t    threadAttribute;
    struct sched_param   scheduleParameter;
    pthread_condattr_t   condAttr;


    HAL_INFO("(%s,%d): stackSize=[%d], prio=[%d]\n",
        __FUNCTION__, __LINE__, stack_size, prio);
    
    if (!g_HAL_Handle.thread_key_init)
    {
        thread_hal_init();
    }

    pthread = malloc_hal(sizeof(THREAD_T));

    if (NULL == pthread)
    {
        HAL_ERR("(%s,%d): Unable to allocate pThreadHandle!",
            __FUNCTION__, __LINE__);
        HAL_ASSERT(0);
    }

    memset(pthread, 0, sizeof(THREAD_T));

    ret = pthread_mutex_init(&pthread->wakeup_mutex,0);
    if (ret != 0)
    {
        HAL_ERR("(%s,%d): Unable to init mutex[%d]!",
            __FUNCTION__, __LINE__, ret);
        HAL_ASSERT(0);
    }

    ret = pthread_condattr_init(&condAttr);
    if (ret != 0) 
    {
        HAL_ERR("(%s,%d): Unable to init condAttr[%d]!",
            __FUNCTION__, __LINE__, ret);
        HAL_ASSERT(0);
    }

#ifndef _WIN32
    ret = pthread_condattr_setclock(&condAttr, CLOCK_REALTIME);
    if (ret != 0) 
    {
        HAL_ERR("(%s,%d): Unable to set clock[%d]!",
            __FUNCTION__, __LINE__, ret);
        HAL_ASSERT(0);
    }
#endif
    ret = pthread_cond_init( &pthread->wakeup_cond, &condAttr);
    if (ret != 0) 
    {
        HAL_ERR("(%s,%d): Unable to init cond[%d]!",
            __FUNCTION__, __LINE__, ret);
        HAL_ASSERT(0);
    }


    pthread->entry_point = entry_point;
    pthread->arg = arg;

    ret = pthread_attr_init(&threadAttribute);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): pthread_attr_init return fail[%d]!",
            __FUNCTION__, __LINE__, ret);
        goto fail_1;
    }

    ret = pthread_attr_setinheritsched(&threadAttribute, PTHREAD_EXPLICIT_SCHED);
    if (ret != 0)
    {
        HAL_ERR("(%s,%d): pthread_attr_setinheritsched returned fail[%d]!",
            __FUNCTION__, __LINE__, ret);
        goto fail_1;
    }

    pthread->policy = DEFAULT_THREAD_SCHED_POLICY;

    ret = pthread_attr_setschedpolicy(&threadAttribute, pthread->policy);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): pthread_attr_setschedpolicy returned fail[%d]!",
            __FUNCTION__, __LINE__, ret);
        goto fail_1;
    }

    /* Stack size should not be less than DEFAULT_MIN_PROCESS_STACK_SIZE. */
    stack_size = (stack_size < DEFAULT_MIN_THREAD_STACK_SIZE) ? DEFAULT_MIN_THREAD_STACK_SIZE : stack_size;

    ret = pthread_attr_setstacksize(
        &threadAttribute,
        stack_size);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): Unable to set thread stack size, stackSize=[%d], ret=[%d]!",
            __FUNCTION__, __LINE__, stack_size, ret);
        goto fail_1;
    }

    pthread->stack_size = stack_size;

    if (pthread->policy != SCHED_OTHER)
    {
        /*Priority is useless when policy is SCHED_OTHER*/
        scheduleParameter.sched_priority = (prio);

        ret = pthread_attr_setschedparam(&threadAttribute, &scheduleParameter);

        if (ret != 0)
        {
            HAL_ERR("(%s,%d): pthread_attr_setschedparam returned fail[%d]!",
                __FUNCTION__, __LINE__, ret);
            goto fail_1;
        }

        pthread->priority = scheduleParameter.sched_priority;
    }

    ret = pthread_create(
        &pthread->thread,
        &threadAttribute,
        thread_hal_launch_thread,
        pthread);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): Unable to create thread[%d]!",
            __FUNCTION__, __LINE__, ret);
        goto fail_1;
    }

    ret = pthread_attr_destroy(&threadAttribute);

    if (ret != 0)
    {
        HAL_ERR("(%s,%d): pthread_attr_destroy returned fail[%d]",
            __FUNCTION__, __LINE__, ret);
        goto fail;
    }


    return (thread_t)pthread; 

fail_1:
    pthread_attr_destroy(&threadAttribute);    
fail:
    pthread_mutex_destroy(&pthread->wakeup_mutex);
    pthread_cond_destroy(&pthread->wakeup_cond);
    free_hal(pthread);

    return 0;
}


void thread_exit(void)
{
    THREAD_T *p_thread = NULL;

    p_thread = (THREAD_T*)thread_self();

    pthread_mutex_destroy(&p_thread->wakeup_mutex);
    pthread_cond_destroy(&p_thread->wakeup_cond);
    
    if (pthread_detach(p_thread->thread))
    {
        HAL_ERR("(%s,%d): pthread_detach returned fail",
            __FUNCTION__, __LINE__);
    }

    free_hal(p_thread);

    if (g_HAL_Handle.thread_cnt > 0)
    {
        g_HAL_Handle.thread_cnt--;
    }

    pthread_exit(0);
}


thread_t thread_self(void)
{
    THREAD_T *pthread = 0;

    pthread =  (THREAD_T*)pthread_getspecific(g_HAL_Handle.thread_key);

    if(NULL == pthread)
    {
        HAL_ERR("Can't get the thread key !!!\n");
    }

    return (thread_t)pthread;
}
#ifdef _WIN32

#define CLOCK_REALTIME 0
static BOOL g_first_time = 1;
static LARGE_INTEGER g_counts_per_sec;

int clock_gettime(int dummy, struct timespec *spec)      //C-file part
{
	__int64 wintime; GetSystemTimeAsFileTime((FILETIME*)&wintime);
	wintime -= 116444736000000000i64;  //1jan1601 to 1jan1970
	spec->tv_sec = wintime / 10000000i64;           //seconds
	spec->tv_nsec = wintime % 10000000i64 * 100;      //nano-seconds
	return 0;
}

int gettimeofday(struct timeval * tp, struct timezone * tzp)
{
	// Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's
	static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL);

	SYSTEMTIME  system_time;
	FILETIME    file_time;
	uint64_t    time;

	GetSystemTime(&system_time);
	SystemTimeToFileTime(&system_time, &file_time);
	time = ((uint64_t)file_time.dwLowDateTime);
	time += ((uint64_t)file_time.dwHighDateTime) << 32;

	tp->tv_sec = (long)((time - EPOCH) / 10000000L);
	tp->tv_usec = (long)(system_time.wMilliseconds * 1000);
	return 0;
}

void sleep(int ms)
{
	Sleep(ms);
}

#endif
void thread_time_sleep(int32_t  ms)
{
    THREAD_T  *pthread = NULL;
    struct timespec now;
    struct timespec target;
    int ret = 0;

    pthread = (THREAD_T*)thread_self();

    clock_gettime(CLOCK_REALTIME, &now);
    
    target.tv_nsec = now.tv_nsec + (ms%1000)*1000000;
    target.tv_sec = now.tv_sec + (ms/1000);
    if (target.tv_nsec >= 1000000000) 
    {
        target.tv_nsec -= 1000000000;
        target.tv_sec ++;
    }

    pthread_mutex_lock(&pthread->wakeup_mutex);

    pthread->sleep = TRUE;

    while(!pthread->wakeup_signal)
    {/* we might have been wokenup and then event has been cleared */
        ret = pthread_cond_timedwait(&pthread->wakeup_cond, &pthread->wakeup_mutex, &target);
        if ((ret == ETIME) || (ret == 0) || (ret == ETIMEDOUT)) 
        {
            /* we either got wakeup signal or time expired. get out of the loop. */
            break;
        }
        else
        {
            HAL_ERR("(%s,%d): Fail to wait wakeup signal!",
                __FUNCTION__, __LINE__);
        }
    }
    
    pthread->wakeup_signal = FALSE;
    pthread->sleep = FALSE;
    pthread_mutex_unlock(&pthread->wakeup_mutex);

}

semaphore_t semaphore_create( int init_value )
{
    Sem_T  *pSem = NULL;

    pSem = malloc_hal(sizeof(Sem_T));
    if(NULL == pSem)
    {
        HAL_ERR("Unable to allocate semaphore! \n");
        return 0;
    }

    pthread_mutex_init(&pSem->mutex,NULL);
    pthread_cond_init(&pSem->cond,NULL);
    pSem->sem = init_value;

    return (semaphore_t)pSem;
}

void semaphore_destroy( semaphore_t sem )
{    
    Sem_T  *pSem = NULL;

    pSem = (Sem_T *)sem;
    pthread_mutex_destroy(&pSem->mutex);
    pthread_cond_destroy(&pSem->cond);

    free_hal(pSem);
    return;
}

void semaphore_signal(semaphore_t sem)
{
    Sem_T  *pSem = NULL;

    pSem = (Sem_T *)sem;

    pthread_mutex_lock(&pSem->mutex);
    pSem->sem++;
    pthread_cond_signal(&pSem->cond);
    pthread_mutex_unlock(&pSem->mutex);

    return;
}

void semaphore_wait(semaphore_t sem)
{
    Sem_T  *pSem = NULL;

    pSem = (Sem_T *)sem;

    pthread_mutex_lock(&pSem->mutex);
    pSem->sem--;
    if(pSem->sem < 0)
    {
        pthread_cond_wait(&pSem->cond, &pSem->mutex);
    }
    pthread_mutex_unlock(&pSem->mutex);

    return;

}


int semaphore_wait_timeout(semaphore_t sem, int timeMs)
{
    Sem_T  *pSem = NULL;
    struct timespec target;
    struct timeval  now;
    int ret = 0;

    pSem = (Sem_T *)sem;

    ret = gettimeofday(&now, NULL);
    if (ret !=0)
    {
        return ret = -1;
    }

    target.tv_nsec = now.tv_usec * 1000 + (timeMs%1000)*1000000;
    target.tv_sec = now.tv_sec + (timeMs/1000);
    if (target.tv_nsec >= 1000000000)
    {
        target.tv_nsec -=  1000000000;
        target.tv_sec ++;
    }
    pthread_mutex_lock(&(pSem->mutex));
    if (pSem->sem <= 0)
    {
        ret = pthread_cond_timedwait(&(pSem->cond), &(pSem->mutex), &target);
    }
    pthread_mutex_unlock(&(pSem->mutex));

    if (ret == 0)
    {
        pthread_mutex_lock(&pSem->mutex);
        pSem->sem--;
        pthread_mutex_unlock(&pSem->mutex);

        return 0;
    }else if(ETIMEDOUT == ret)
    {
        HAL_ERR("TimeOut to get Semaphore !\n");
        return HAL_TIMEOUT;
    }
    else
        return HAL_ERROR;
}


mutex_t mutex_create(void)
{

    MUTEX_T *pMux = NULL;

    pMux = malloc_hal(sizeof(MUTEX_T));
    if(NULL == pMux)
    {
        HAL_ERR("Unable to allocate for Mutex!\n");
        return 0;
    }
    
    pthread_mutex_init(&(pMux->mutex), 0);

    return (mutex_t)pMux;
}

void mutex_lock(mutex_t mutex)
{
    MUTEX_T *pMux = NULL;

    pMux = (MUTEX_T *)mutex;
    if(NULL == pMux){
        HAL_ERR("(%s:%d) Parameter Error.\n",__FUNCTION__,__LINE__);
    }
    if(0 != pthread_mutex_lock(&(pMux->mutex)))
    {
        HAL_ERR("Can't get lock for mutex %p \n", pMux);
    }

    return;
}

void mutex_unlock(mutex_t mutex)
{
    MUTEX_T *pMux = NULL;

    pMux = (MUTEX_T *)mutex;
    if(NULL==pMux){
        HAL_ERR("(%s:%d) Parameter Error.\n",__FUNCTION__,__LINE__);
    }
    if(0 != pthread_mutex_unlock(&(pMux->mutex)))
    {
        HAL_ERR("Can't release lock for mutex %p \n", pMux);
    }

    return;

}

void mutex_destroy(mutex_t mutex)
{

    MUTEX_T *pMux = NULL;

    pMux = (MUTEX_T *)mutex;
    if(0 != pthread_mutex_destroy(&(pMux->mutex)))
    {
        HAL_ERR("Can't destroy for mutex %p \n", pMux);
    }

    free_hal(pMux);
 
    return;
}

void *malloc_hal(int32_t size)
{
    return malloc(size);
}

void  free_hal(void *mem)
{
    return free(mem);
}

